package prevote;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.math.BigInteger;
import java.net.ConnectException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.security.interfaces.RSAPublicKey;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;

import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLSocket;
import javax.swing.JFileChooser;
import javax.swing.SwingWorker;

import protocol.Info;
import registration.RegistrationResources;
import sslsockets.SSLSocketHandler;
import vote.VotePanelDescriptor;
import wizard.Wizard;
import wizard.WizardPanelDescriptor;
import client.WelcomePanelDescriptor;

import com.google.common.collect.Lists;

import data.Blind;
import data.CardsManager;
import data.VotingSet;

public class PreVotePanelDescriptor extends WizardPanelDescriptor implements ActionListener
{
    private class InfoTextHandler extends SwingWorker<Boolean, String>
    {
        public InfoTextHandler()
        {
        }

        @Override
        protected Boolean doInBackground() throws Exception
        {
            publish( "Connecting to the server..." );
            Boolean result = new Boolean( false );
            try
            {
                KeyStore trustKeyStore = KeyStore.getInstance( KeyStore.getDefaultType() );
                KeyStore keyStore = KeyStore.getInstance( KeyStore.getDefaultType() );
                FileInputStream fis = new FileInputStream( trustKeyStoreFile );
                trustKeyStore.load( fis, trustKeyStorePassword );
                // get PKW's public key
                PKWPublicKey = trustKeyStore.getCertificate( "PKW" ).getPublicKey();
                fis.close();
                fis = new FileInputStream( keyStoreFile );
                keyStore.load( fis, keyStorePassword );
                System.out.println( "Client: loaded keyStore with aliases: " );
                Enumeration<String> en = keyStore.aliases();
                String myPesel = "";
                for( ; en.hasMoreElements(); )
                {
                    myPesel = en.nextElement();
                }
                System.out.println( "Klient: mam pesel!! -> " + myPesel );
                System.out.println( "Client: trying to connect to port: " + Info.VOTE_PORT );
                SSLSocket sslSocket = SSLSocketHandler
                        .createSocket( Info.HOSTIN, Info.VOTE_PORT, trustKeyStore, keyStore, keyStorePassword );
                sslSocket.setNeedClientAuth( true );
                publish( "Connected to the server" );
                OutputStream out = sslSocket.getOutputStream();
                ObjectOutputStream oout = new ObjectOutputStream( out );
                publish( "Sending data for validation" );
                oout.writeObject( myPesel );
                oout.writeObject( Info.MSG_CANDIDATES );
                System.out.println( "wyslalem prosbe o kandydatow." );
                InputStream in = sslSocket.getInputStream();
                ObjectInputStream oin = new ObjectInputStream( in );
                Object received = null;
                try
                {
                    received = oin.readObject();
                }
                catch( Exception e )
                {
                    publish( "<html>There has been a problem with a connection to PKW.<br>Check your Internet connection and try again.</html>" );
                    return result;
                }
                // BLINDING ITD
                // DOSTAJEMY KANDYDATOW
                String candidates_message = (String)received;
                System.out.println( "Client: dostalem kandydatow: " + candidates_message );
                if( candidates_message.startsWith( Info.MSG_CANDIDATES ) )
                {
                    // utnij 'candidates' z wiadomosci
                    candidates_message = candidates_message.substring( candidates_message.indexOf( Info.MSG_CANDIDATES )
                            + Info.MSG_CANDIDATES.length() );
                    candidates = Arrays.asList( candidates_message.split( "\r\n" ) );
                    publish( "Received candidates." );
                }
                publish( "Validation complete. Please wait, it may take up to 30 seconds to check your vote sets..." );
                // client connection
                // server sends card to him
                // CLIENT PART
                // we have clients in candidates_message
                System.out.println( "Klient: do CardManagera wysylam -> " + candidates_message );
                System.out.println( "Klient: candidates ma dlugosc -> " + candidates.size() );
                for( String s: candidates )
                {
                    System.out.println( "Klient: candidates z listy -> " + s );
                }
                getWizard().storePanelData( IDENTIFIER + "candidates", candidates );
                CardsManager manager = new CardsManager( candidates_message );
                List<VotingSet> sets = manager.exportSets();
                // blind all sets
                List<BigInteger> tab = Lists.newLinkedList(); // table of
                                                              // blinding
                                                              // values
                List<String> blindedSets = Lists.newLinkedList();
                Blind b = new Blind();
                System.out.println( "Klient: pub ->" + PKWPublicKey.toString() );
                for( VotingSet set: sets )
                    blindedSets.add( b.doBlind( set, (RSAPublicKey)PKWPublicKey, tab ) );
                // client sends all blindedsets
                System.out.println( "Klient: wysylam sety." );
                for( String s: blindedSets )
                {
                    // System.out.println( "Klient: " + s );
                    oout.writeObject( s );
                    oout.flush();
                }
                // teraz server wybiera jakiegos randoma i nam wysyla
                // odbieram randoma
                int randomServerNumber = Integer.parseInt( (String)oin.readObject() );
                System.out.println( "Klient: przeczytalem randoma" );
                StringBuilder response = new StringBuilder();
                int setsNumber = 100; // actually hardcoded in cards manager
                for( int i = 0; i < setsNumber; i++ )
                {
                    if( i == randomServerNumber )
                        continue;
                    response.append( tab.get( i ) + "\r\n" );
                }
                String sentResponse = response.toString();
                oout.writeObject( sentResponse );
                System.out.println( "Klient: wyslalem dane po tym randomie" );
                // czekamy na to az server albo nam powie, ze oszukujemy i won
                // albo wysle nam podpisanego seta
                received = oin.readObject();
                String server_message = (String)received;
                if( server_message.startsWith( Info.MSG_ERROR_BLINDING ) )
                {
                    publish( Info.MSG_ERROR_BLINDING );
                }
                else
                {
                    getWizard().storePanelData( IDENTIFIER + "server_message", server_message );
                    getWizard().storePanelData( IDENTIFIER + "blind_parameter", tab.get( randomServerNumber ) );
                    getWizard().storePanelData( IDENTIFIER + "random_number", randomServerNumber );
                    getWizard().storePanelData( IDENTIFIER + "sets", sets );
                    System.out.println( "Wszystko poszlo swietnie, zapisane dane w wizardzie i jedziemy do glosowania" );
                    publish( "You may vote now." );
                }
                // KONIEC CALEJ ZABAWY Z BLINDINGIEM, MOZNA GLOSOWAC
                sslSocket.close();
                oout.close();
                fis.close();
                oin.close();
            }
            catch( ConnectException e )
            {
                publish( "<html>There has been a problem with a connection to PKW.<br>Check your Internet connection and try again.</html>" );
                e.printStackTrace();
                return result;
            }
            catch( SSLHandshakeException e )
            {
                publish( "Your certificate is invalid. Please register first." );
                e.printStackTrace();
                return result;
            }
            catch( KeyStoreException e )
            {
                e.printStackTrace();
                getWizard().close( Wizard.ERROR_RETURN_CODE );
            }
            catch( FileNotFoundException e )
            {
                e.printStackTrace();
                getWizard().close( Wizard.ERROR_RETURN_CODE );
            }
            catch( NoSuchAlgorithmException e )
            {
                e.printStackTrace();
                getWizard().close( Wizard.ERROR_RETURN_CODE );
            }
            catch( CertificateException e )
            {
                e.printStackTrace();
                getWizard().close( Wizard.ERROR_RETURN_CODE );
            }
            catch( IOException e )
            {
                publish( "There has been an error, probably the password is wrong." );
                e.printStackTrace();
                return result;
            }
            catch( UnrecoverableKeyException e )
            {
                e.printStackTrace();
                getWizard().close( Wizard.ERROR_RETURN_CODE );
            }
            result = new Boolean( true );
            getWizard().setBackButtonEnabled( false );
            getWizard().setCancelButtonEnabled( false );
            publish( "Now you have to vote! There's no way back" );
            getWizard().setNextFinishButtonEnabled( true );
            return result;
        }

        public void process( List<String> communicates )
        {
            String infoText = "";
            for( String s: communicates )
            {
                infoText += ( s + "<br>" );
            }
            pvp.setInfoText( infoText );
        }
    }

    public static String IDENTIFIER = "PRE_VOTE_PANEL";
    private PreVotePanel pvp;
    private List<String> candidates;
    private String keyStoreFile;
    private char[] keyStorePassword;
    private static String trustKeyStoreFile;
    private static char[] trustKeyStorePassword;
    private PublicKey PKWPublicKey;
    static
    {
        RegistrationResources resources = new RegistrationResources();
        trustKeyStoreFile = (String)resources.getObject( "trustKeyStoreFile" );
        trustKeyStorePassword = (char[])resources.getObject( "trustKeyStorePassword" );
    }

    public PreVotePanelDescriptor()
    {
        pvp = new PreVotePanel();
        pvp.addSubmitButtonActionListener( this );
        setPanelDescriptorIdentifier( IDENTIFIER );
        setPanelComponent( pvp );
    }

    public String getNextPanelDescriptor()
    {
        return VotePanelDescriptor.IDENTIFIER;
    }

    public String getBackPanelDescriptor()
    {
        return WelcomePanelDescriptor.IDENTIFIER;
    }

    public void aboutToDisplayPanel()
    {
        getWizard().setNextFinishButtonEnabled( false );
    }

    @Override
    public void actionPerformed( ActionEvent arg0 )
    {
        System.out.println( "submit clicked." );
        JFileChooser chooser = new JFileChooser();
        chooser.setDialogTitle( "Load previously saved certificate." );
        int returnVal = chooser.showOpenDialog( getWizard().getDialog() );
        if( returnVal == JFileChooser.APPROVE_OPTION )
        {
            keyStoreFile = chooser.getSelectedFile().getPath();
            keyStorePassword = pvp.getSecretCodeText().toCharArray();
            InfoTextHandler ith = new InfoTextHandler();
            ith.execute();
        }
    }
}
